package com.pty.test;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import lombok.Data;

import com.google.common.collect.Lists;

public class jdk8 {
	public static final void main(String[] arg) throws Exception {
		Runnable r = () -> System.out.println("In Java8, Lambda expression rocks !!");
		new Thread(r).start();

		Lists.newArrayList("1", "2", "3").stream().forEach(System.out::println);

		Consumer<String> c = (s) -> System.out.println(s);
		test(c, "1");
		c.accept("2");

		testDate();

		testUtil();

		// Arrays.asList(jdk8.class.getMethods()).forEach((m) ->
		// System.out.println(m.getName()));

		Method method = jdk8.class.getMethod("testParam", int.class);
		Arrays.asList(method.getParameters()).forEach((p) -> System.out.println(p.getName()));

		testInterfaces();

		testOptional(Optional.empty());

		testOptional(Optional.of("Bob"));

		testParaelStream();

		testStream1();

	}

	private static <T> void test(Consumer<T> c, T t) {
		c.accept(t);
	}

	private static void testDate() {
		String startMonth = "201309";
		YearMonth startDate = YearMonth.parse(startMonth, DateTimeFormatter.ofPattern("yyyyMM"));
		YearMonth endDate = YearMonth.parse("201402", DateTimeFormatter.ofPattern("yyyyMM"));
		while (endDate.compareTo(startDate) >= 0) {
			System.out.println(startDate.getMonth());
			startDate = startDate.plusMonths(1);
		}

		LocalDate localDate = LocalDate.now();
		System.out.println(localDate.getDayOfMonth());

		LocalDateTime localDateTime = LocalDateTime.now();
		System.out.println(localDateTime.getHour());

	}

	private static void testUtil() {
		String encoded = Base64.getEncoder().encodeToString("123".getBytes(StandardCharsets.UTF_8));
		System.out.println(encoded);
		String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
		System.out.println(decoded);
	}

	public void testParam(int abc) {
		System.out.println("parmater name test!");
	}

	private static void testInterfaces() {
		Action<String> action = DefaulableFactory.create(DefaultableImpl<String>::new);
		action.run(100);
	}

	private static Optional<String> testOptional(Optional<String> o) {
		String str = o.orElseGet(() -> "default");
		System.out.println(str);
		Optional<String> firstName = Optional.of(str);
		if (firstName.isPresent())
			System.out.println(firstName.get());

		System.out.println(firstName.map((s) -> "hello " + s + "!").orElse("Hey Alex!"));

		return Optional.ofNullable("optional");
	}

	private static void testParaelStream() {
		Predicate<Task> pred = task -> task.getStatus() == Status.OPEN;

		int sum = Arrays
				.asList(new Task(Status.OPEN, 5, 1), new Task(Status.OPEN, 13, 2), new Task(Status.CLOSED, 8, 3))
				.parallelStream()
				// .filter( task -> task.getStatus() == Status.OPEN )
				.filter(pred).mapToInt((t) -> {
					System.out.println(Thread.currentThread().getName());
					try {
						Thread.sleep(t.getId());
					} catch (Exception e) {
						e.printStackTrace();
					}

					return t.getPoints();
				}).sum();

		System.out.println("sum:" + sum);

	}

	private static void testStream1() {
		// flatMap
		int sum = Stream
				.of(Arrays.asList(new Task(Status.OPEN, 5, 1, new BigDecimal(100)), new Task(Status.OPEN, 13, 2,
						new BigDecimal(100)), new Task(Status.CLOSED, 8, 3, new BigDecimal(100))))
				.flatMap(l -> l.stream()).mapToInt(Task::getPoints).sum();
		System.out.println("sum:" + sum);

		// reduce
		BigDecimal balance = Arrays
				.asList(new Task(Status.OPEN, 5, 1, new BigDecimal(100)),
						new Task(Status.OPEN, 13, 2, new BigDecimal(100)),
						new Task(Status.CLOSED, 8, 3, new BigDecimal(100))).parallelStream().map(Task::getBalance)
				.reduce(BigDecimal.ZERO, BigDecimal::add);
		System.out.println("balance:" + balance);

		// collect
		List<Task> tasks = Arrays
				.asList(new Task(Status.OPEN, 5, 1, new BigDecimal(100)),
						new Task(Status.OPEN, 13, 2, new BigDecimal(100)),
						new Task(Status.CLOSED, 8, 3, new BigDecimal(100))).parallelStream()
				.filter(t -> t.getStatus() == Status.OPEN).collect(Collectors.toList());
		System.out.println("task:" + tasks.size());

		// groupby
		Map<Status, List<Task>> TaskByStatus = Arrays
				.asList(new Task(Status.OPEN, 5, 1, new BigDecimal(100)),
						new Task(Status.OPEN, 13, 2, new BigDecimal(100)),
						new Task(Status.CLOSED, 8, 3, new BigDecimal(100))).parallelStream()
				.collect(Collectors.groupingBy(Task::getStatus));
		System.out.println("groupby:" + TaskByStatus.size());

		Map<Status, Map<String, List<Task>>> TaskByStatusAndType = Arrays
				.asList(new Task(Status.OPEN, 5, 1, new BigDecimal(100), "home"),
						new Task(Status.OPEN, 13, 2, new BigDecimal(100), "work"),
						new Task(Status.CLOSED, 8, 3, new BigDecimal(100), "home")).parallelStream()
				.collect(Collectors.groupingBy(Task::getStatus, Collectors.groupingBy(Task::getType)));
		System.out.println("groupby:" + TaskByStatusAndType.size());
		System.out.println("staus and type:" + TaskByStatusAndType);

		List<Task> taskList = Arrays.asList(new Task(Status.OPEN, 5, 1, new BigDecimal(100), "work"), new Task(
				Status.OPEN, 13, 2, new BigDecimal(100)), new Task(Status.CLOSED, 8, 3, new BigDecimal(100)), new Task(
				Status.CLOSED, 6, 3, new BigDecimal(100), "work"));
		Map<Status, Double> groupAve = taskList.parallelStream().collect(
				Collectors.groupingBy(Task::getStatus, Collectors.averagingInt(Task::getPoints)));
		System.out.println("groupbyAve:" + groupAve);

		Map<Status, Integer> groupsum = taskList.parallelStream().collect(
				Collectors.groupingBy(Task::getStatus, Collectors.summingInt(Task::getPoints)));
		System.out.println("groupbySum:" + groupsum);

		Map<Status, List<TaskVO>> groupmapping = taskList.parallelStream().collect(
				Collectors.groupingBy(
						Task::getStatus,
						Collectors.mapping(t -> new TaskVO(t.getStatus(), t.getBalance(), t.getType()),
								Collectors.toList())));
		System.out.println("groupbymapping:" + groupmapping);

		Stream.of(1, 5, 2, 8, 10, 45, 4, 7, 32, 89, 1000, 22, 1, 9).distinct().sorted().forEach(System.out::println);
	}

	private static enum Status {
		OPEN, CLOSED
	};

	@Data
	private static final class Task {
		private final Status status;
		private final Integer points;
		private final Integer id;
		private final BigDecimal balance;
		private final String type;

		public Task(Status s, Integer p, Integer i) {
			this(s, p, i, BigDecimal.ZERO);
		}

		public Task(Status s, Integer p, Integer i, BigDecimal b) {
			this(s, p, i, b, "home");
		}

		public Task(Status s, Integer p, Integer i, BigDecimal b, String t) {
			status = s;
			points = p;
			id = i;
			balance = b;
			type = t;
		}
	}

	@Data
	private static final class TaskVO {
		private final Status status;
		private final BigDecimal balance;
		private final String type;
	}

	@FunctionalInterface
	public static interface Action<R> {
		R run(int i);

		default String notRequired() {
			return "Default implementation";
		}
	}

	public static class DefaultableImpl<R> implements Action<R> {
		R r;

		public R run(int i) {
			System.out.println("default Run");
			return r;
		}
	}

	public static interface DefaulableFactory {
		// Interfaces now allow static methods
		static <R> Action<R> create(Supplier<Action<R>> supplier) {
			return supplier.get();
		}
	}
}
